﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;
using Opennav.MapGenerator.OSM;

namespace Opennav.MapGenerator.OSM
{
    public class BinWaysCache : IEnumerable<OSMWay>
    {
        private FileStream _wayStream;
        private FileStream _idxStream;
        private int idxElementSize = sizeof(uint) + sizeof(long);
        private uint lastId = 0;

        private string wayFile, indexFile;

        public BinWaysCache(string tempFolder, bool reopen)
        {
            FileMode mode = FileMode.Create;
            if (reopen)
                mode = FileMode.Open;
            wayFile = Path.Combine(tempFolder, "ways.tmp");
            indexFile = Path.Combine(tempFolder, "ways.x");
            _wayStream = new FileStream(wayFile, mode, FileAccess.ReadWrite);
            _idxStream = new FileStream(indexFile, mode, FileAccess.ReadWrite);
            if (Count > 0)
            {
                _idxStream.Seek((Count - 1) * idxElementSize, SeekOrigin.Begin);
                lastId = (new BinaryReader(_idxStream)).ReadUInt32();
            }
        }

        public void Close()
        {
            _wayStream.Flush();
            _wayStream.Close();
            _idxStream.Flush();
            _idxStream.Close();
            File.Delete(wayFile);
            File.Delete(indexFile);
        }

        public void Add(OSMWay way)
        {
            if (lastId > 0 && lastId >= way.Id)
                throw new InvalidOperationException("ways out of order");
            lastId = way.Id;
            BinaryWriter idxWriter = new BinaryWriter(_idxStream);
            idxWriter.Seek(0, SeekOrigin.End);
            BinaryWriter wayWriter = new BinaryWriter(_wayStream);
            wayWriter.Seek(0, SeekOrigin.End);
            long offset = wayWriter.BaseStream.Position;
            wayWriter.Write((ushort)way.Nodes.Count);
            foreach (uint nodeId in way.Nodes)
            {
                wayWriter.Write(nodeId);
            }
            wayWriter.Write((ushort)way.Tags.Count);
            foreach (OSMTag tag in way.Tags)
            {
                wayWriter.Write(tag.Key);
                wayWriter.Write(tag.Value);
            }
            idxWriter.Write(way.Id);
            idxWriter.Write(offset);
        }

        public OSMWay Get(uint id)
        {
            BinaryReader idxReader = new BinaryReader(_idxStream);
            int middle;
            int left = 0;
            int elements = Count;
            int right = elements - 1;
            while (true)
            {
                middle = left + ((right - left) / 2);
                if (right < left)
                {
                    return null;
                }
                idxReader.BaseStream.Seek(middle * idxElementSize, SeekOrigin.Begin);
                uint elementId = idxReader.ReadUInt32();
                if (elementId == id)
                {
                    return ReadWay(id);
                }
                if (elementId > id)
                    right = middle - 1;
                else
                    left = middle + 1;
            }
        }

        public int Count
        {
            get
            {
                return (int)(_idxStream.Length / idxElementSize);
            }
        }

        public IEnumerator<OSMWay> GetEnumerator()
        {
            for (int i = 0; i < Count; i++)
            {
                _idxStream.Seek(i * idxElementSize, SeekOrigin.Begin);
                uint wayId = (new BinaryReader(_idxStream)).ReadUInt32();
                yield return ReadWay(wayId);
            }
        }

        System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
        {
            return GetEnumerator();
        }

        private OSMWay ReadWay(uint id)
        {
            BinaryReader idxReader = new BinaryReader(_idxStream);
            long offset = idxReader.ReadInt64();
            BinaryReader wayReader = new BinaryReader(_wayStream);
            wayReader.BaseStream.Seek(offset, SeekOrigin.Begin);
            ushort nNodes = wayReader.ReadUInt16();
            IList<uint> nodeIds = new List<uint>();
            for (int n = 0; n < nNodes; n++)
            {
                uint coordId = wayReader.ReadUInt32();
                nodeIds.Add(coordId);
            }
            ushort ntags = wayReader.ReadUInt16();
            IList<OSMTag> tags = new List<OSMTag>();
            for (int ntag = 0; ntag < ntags; ntag++)
            {
                string key = wayReader.ReadString();
                string value = wayReader.ReadString();
                tags.Add(new OSMTag() { Key = key, Value = value });
            }
            return new OSMWay() { Id = id, Nodes = nodeIds, Tags = tags };
        }
    }
}
